﻿//-------------------------------------------------------------------------------------------------
// <copyright file="CypherEngine.cs" company="BeeWix">
// BeeWix. Licensed under MIT
// </copyright>
//-------------------------------------------------------------------------------------------------

namespace BeeWix.Toolkit.NoSql.Neo4J.Cypher
{
    using System;
    using BeeWix.Toolkit.Dynamic;
    using BeeWix.Toolkit.Http;
    using System.Linq;
    using BeeWix.Toolkit.NoSql.Neo4J.LowLevel;
    using System.Collections.Generic;

    /// <summary>
    /// CypherEngine class
    /// </summary>
    public static class CypherEngine
    {
        /// <summary>
        /// Application/Json type
        /// </summary>
        private const string APPJSON = "application/json";

        /// <summary>
        /// Private fields linked to the Database property
        /// </summary>
        private static string pDataBase = "http://localhost:7474";
        
        /// <summary>
        /// Gets or sets the current database path
        /// </summary>
        public static string DataBase 
        { 
            get 
            {
                return pDataBase;
            } 

            set 
            {
                pDataBase = value;
            } 
        }        
        
        /// <summary>
        /// Get the node by its id
        /// </summary>
        /// <param name="id">id of the node</param>
        /// <param name="database">Optional database path (default is localhost:7474)</param>
        /// <returns>Returns a new node</returns>
        public static Neo4JNode GetNode(int id, string database = "")
        {
            if (string.IsNullOrWhiteSpace(database))
            {
                database = pDataBase;
            }

            return GetNode(database + "/db/data/node/" + id);                               
        }

        /// <summary>
        /// Get the node by its id
        /// </summary>
        /// <param name="id">id of the node</param>
        /// <param name="database">Optional database path (default is localhost:7474)</param>
        /// <returns>Returns a new node</returns>
        public static T GetNode<T>(int id, string database = "")
        {
            var res = GetNode(id, database);    
            
            var ret = Activator.CreateInstance<T>();
            (ret as Neo4JNode).jsonData = res.jsonData;
            (ret as Neo4JNode).FromData();
            return ret;
        }

        /// <summary>
        /// Gets the node by its full url
        /// </summary>
        /// <param name="selfUrl">The url of the node</param>
        /// <returns>Returns a new node</returns>
        public static Neo4JNode GetNode(string selfUrl)
        {
            var runner    = new HttpRunner(selfUrl);            
            runner.Accept = APPJSON;
            runner.SetMethod(HttpRunner.RequestMethod.Get);
            runner.ExecuteRequest();

            if (runner.Status == System.Net.HttpStatusCode.OK)
            {
                return new Neo4JNode()
                {
                    jsonData = runner.GetResponse()
                };
            }
            else if (runner.Status == System.Net.HttpStatusCode.NotFound)
            {
                return new Neo4JNode();
            }
            else
            {
                throw new Exception("Cannot retreive node");
            }
        }

        /// <summary>
        /// Gets the node by its full url
        /// </summary>
        /// <param name="selfUrl">The url of the node</param>
        /// <returns>Returns a new node</returns>
        public static T GetNode<T>(string selfUrl)
        {
            var res = GetNode(selfUrl);  
  
             var ret = Activator.CreateInstance<T>();
            (ret as Neo4JNode).jsonData = res.jsonData;
            (ret as Neo4JNode).FromData();
            return ret;
        }

         /// <summary>
        /// Creates a new node
        /// </summary>
        /// <returns></returns>
        public static T CreateNode<T>(string database = "")
        {
             var res = CreateNode(database);  
  
             var ret = Activator.CreateInstance<T>();
            (ret as Neo4JNode).jsonData = res.jsonData;
            (ret as Neo4JNode).FromData();
            return ret;
        }

        /// <summary>
        /// Creates a new node
        /// </summary>
        /// <returns>Returns a new node</returns>
        public static Neo4JNode CreateNode(string database = "")
        {
            if (string.IsNullOrWhiteSpace(database))
            {
                database = pDataBase;
            }

            var runner    = new HttpRunner(database + "/db/data/node");            
            runner.Accept = APPJSON;
            runner.SetMethod(HttpRunner.RequestMethod.Post);
            runner.ExecuteRequest();

            if (runner.Status == System.Net.HttpStatusCode.Created)
            {
                return new Neo4JNode()
                {
                    jsonData = runner.GetResponse()
                };
            }
            else
            {
                throw new Exception(DynamicJson.Parse(runner.GetResponse()).message as string);
            }     
        }

        public static bool DeleteNode(object node, bool forceIfNotOrphan = false)
        {
            if (node is Neo4JNode)
            {
                if ((node as Neo4JNode).Data.IsDefined("self"))
                {
                    
                    if (forceIfNotOrphan)
                    {
                        var runnerD    = new HttpRunner((node as Neo4JNode).Data.self + "/relationships/all");
                        runnerD.Accept = APPJSON;
                        runnerD.SetMethod(HttpRunner.RequestMethod.Get);
                        runnerD.ExecuteRequest();

                        if (runnerD.Status == System.Net.HttpStatusCode.OK)
                        {
                            var dynR = DynamicJson.Parse(runnerD.GetResponse());

                            foreach(var itm in (dynamic[])dynR)
                            {
                                runnerD    = new HttpRunner(itm.self);            
                                runnerD.Accept = APPJSON;
                                runnerD.SetMethod(HttpRunner.RequestMethod.Delete);
                                runnerD.ExecuteRequest();
                            }
                        }
                    }
                    
                    var runner    = new HttpRunner((node as Neo4JNode).Data.self);            
                    runner.Accept = APPJSON;
                    runner.SetMethod(HttpRunner.RequestMethod.Delete);
                    runner.ExecuteRequest();

                    if (runner.Status == System.Net.HttpStatusCode.NoContent)
                    {
                        return true;
                    }
                }
            }
            
            return false;
        }

        public static List<Neo4JNode> Query(string query, string database = "")
        {
            var cq = new CypherQuery()
            {
                query = query,
                @params = new Neo4JNode()
            };

            if (string.IsNullOrWhiteSpace(database))
            {
                database = pDataBase;
            }

            var runner    = new HttpRunner(database + "/db/data/cypher");        
            runner.Accept = APPJSON;
            runner.SetMethod(HttpRunner.RequestMethod.Post);
            runner.SetPostContent(cq.ToJson(), HttpRunner.RequestPostEncoding.UTF8);
            runner.ExecuteRequest();

            var res = new List<Neo4JNode>();

            if (runner.Status == System.Net.HttpStatusCode.OK)
            {
                var dyn = DynamicJson.Parse(runner.GetResponse());
                

                if ( ((dynamic[])dyn.data).Length == 1)
                {
                    var itm = new Neo4JNode();
                    for(var i=0; i<((dynamic[])((dynamic[])dyn.data)[0]).Length;i++)
                    {
                        itm.SetProperty(((dynamic[])dyn.columns)[i].Replace("?","_"),((dynamic[])((dynamic[])dyn.data)[0])[i]);
                    }
                    res.Add(itm);
                }
            }

            return res;
        }

         public static List<T> Query<T>(string query, string database = "") where T : Neo4JNode
        {
            var cq = new CypherQuery()
            {
                query = query,
                @params = new Neo4JNode()
            };

            if (string.IsNullOrWhiteSpace(database))
            {
                database = pDataBase;
            }

            var runner    = new HttpRunner(database + "/db/data/cypher");        
            runner.Accept = APPJSON;
            runner.SetMethod(HttpRunner.RequestMethod.Post);
            runner.SetPostContent(cq.ToJson(), HttpRunner.RequestPostEncoding.UTF8);
            runner.ExecuteRequest();

            var res = new List<T>();

            if (runner.Status == System.Net.HttpStatusCode.OK)
            {
                var dyn = DynamicJson.Parse(runner.GetResponse());
                

                if ( ((dynamic[])dyn.data).Length > 0)
                {
                    for(var i=0; i<((dynamic[])dyn.data).Length;i++)
                    {
                        var itm = Activator.CreateInstance<T>();
                        itm.jsonData =  ((dynamic[])((dynamic[])dyn.data)[i])[0].ToString();
                        itm.FromData();
                        res.Add(itm);
                    }                    
                }
            }

            return res;
        }

        /// <summary>
        /// Update all properties of a node
        /// </summary>
        /// <param name="node">The node to update</param>
        public static bool UpdateNodeProperties (object node, bool forceIndexUpdate = false)
        {
            if (node is Neo4JNode)
            {
                var nd = node as Neo4JNode;

                var runner = new HttpRunner(nd.Data.properties);
                runner.Accept = APPJSON;
                runner.SetMethod(HttpRunner.RequestMethod.Put);
                runner.SetPostContent(nd.ToJson(), HttpRunner.RequestPostEncoding.UTF8);
                runner.ExecuteRequest();

                if (runner.Status != System.Net.HttpStatusCode.NoContent)
                {
                    return false;
                }

                //checks index node
                var props = node.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).Where(p => p.GetCustomAttributes(true).OfType<Neo4JNodeIndexAttribute>().Count() > 0);
                    
                foreach(var pr in props)
                {
                    var indexName  = pr.GetCustomAttributes(true).OfType<Neo4JNodeIndexAttribute>().First().IndexName;
                    var fieldName  = pr.Name;
                    var fieldValue = pr.GetValue(node,null);
                    var nodeUrl    = nd.Data.self as string;
                    var postContent = new NodeToIndex()
                    {
                        uri   = nodeUrl,
                        key   = fieldName,
                        value = fieldValue
                    };

                    if (!forceIndexUpdate)
                    {
                        if (nd.Data.data.IsDefined(fieldName) && nd.GetProperty(fieldName) == fieldValue.ToString())
                        {
                            return true;
                        }
                    }
                    
                    string nodeDBUrl = nodeUrl.Substring(0,nodeUrl.IndexOf("/db"));
                    
                    runner = new HttpRunner(nodeDBUrl + "/db/data/index/node/kvnode/" + indexName + "/" + (nd.Data.self as string).Split('/').Last());
                    runner.Accept = APPJSON;
                    runner.SetMethod(HttpRunner.RequestMethod.Delete);
                    runner.ExecuteRequest();

                    //Ajout de l'index
                    runner = new HttpRunner(nodeDBUrl + "/db/data/index/node/" + indexName);
                    runner.Accept = APPJSON;
                    runner.SetContentType(APPJSON);
                    runner.SetMethod(HttpRunner.RequestMethod.Post);
                    runner.SetPostContent(postContent.ToJson(), HttpRunner.RequestPostEncoding.UTF8);
                    runner.ExecuteRequest();

                    if (runner.Status != System.Net.HttpStatusCode.Created)
                    {
                        return false;
                    }
                    
                }
            }

            return true;
        }

        public static bool CreateRelationship(Neo4JNode from, Neo4JNode to, string type)
        {
            if (from.Data.IsDefined("self") && to.Data.IsDefined("self"))
            {
                var postContent = new ToTypeRelationship()
                {
                    to   = to.Data.self,
                    type = type
                };
                
                var runner    = new HttpRunner(from.Data.self + "/relationships");
                runner.Accept = APPJSON;
                runner.SetContentType(APPJSON);
                runner.SetMethod(HttpRunner.RequestMethod.Post);
                runner.SetPostContent(postContent.ToJson(), HttpRunner.RequestPostEncoding.UTF8);
                runner.ExecuteRequest();

                if (runner.Status == System.Net.HttpStatusCode.Created)
                {
                    return true;
                }
            }

            return false;
        }
    }
}
